import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;


public class ServerThread extends Thread {
	
	private BufferedReader in;
	private PrintWriter out;
	private String lasterror;
	private static Logger log;
	private TripleDb tripleDb;
	private Socket sock;
	private RegistryServer server;
	
	private final String PORT_IN_USE_ERR = "0x001";
	private final String PROTO_SEQUENCE_ERR = "0x002";
	private final String INCORRECT_PROTOCOL_ERR = "0x003";
	private final String IO_EXCEPTION_ERR = "0x004";
	private final String UNKNOWN_PROTOCOL_ERR = "0x005";
	private final String FALSE = "FALSE";
	private final String TRUE = "TRUE";
	
	public ServerThread(Socket sock, RegistryServer server) throws IOException
	{
		this.server = server;
		this.sock = sock;
		log = Logger.getLogger();
		tripleDb = TripleDb.getTripleDb();
		in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
		out = new PrintWriter( new BufferedWriter(new OutputStreamWriter(sock.getOutputStream())));
	}
	
	private void send(String str) throws IOException
	{
		out.println(str);
		out.flush();
	}
	
	private String receive()throws IOException
	{
		return in.readLine();
	}
	
	private void error(String code) throws IOException
	{
		lasterror = code;
		send("ERROR " + code );
	}

	public static List<String> sanitize(String[] inp )  
	{
		List<String> strList = new ArrayList<String>();
		
		for(String str: inp)
		{
			if(str.equals(""))
				continue;
			strList.add(str);
		}
		
		return strList;
	}
	
	private void isPrefLength(List<String> strList, int prefsize) throws IOException
	{
		if(strList.size() != prefsize)
		{
			log.error(prefsize + "!= String Length : " + strList.size() + " " + strList);
			error(INCORRECT_PROTOCOL_ERR);
			throw new IOException("Incorrect Command");
		}
		
	}
	
	private boolean parse_and_run(String cmd) throws IOException
	{
		List<String> cmdlist = sanitize(cmd.split(" "));
		
		if(cmdlist.size() == 0)
			return true;
		
		if(cmdlist.get(0).equalsIgnoreCase("REGISTER"))
		{
			isPrefLength(cmdlist, 4);
			/*Add to database*/
			tripleDb.add(cmdlist.get(1), cmdlist.get(2) , cmdlist.get(3));
			send("STATUS " + TRUE);
			return true;
		}
		
		if(cmdlist.get(0).equalsIgnoreCase("REMOVE"))
		{
			isPrefLength(cmdlist, 4);
			
			lasterror = tripleDb.remove(cmdlist.get(1), cmdlist.get(2), cmdlist.get(3)) ? TRUE : FALSE ;
			send("STATUS " + lasterror);
			return true;
		}
		
		if(cmdlist.get(0).equalsIgnoreCase("QUERY"))
		{
			isPrefLength(cmdlist, 4);
			
			String id = cmdlist.get(1);
			List<String> result = tripleDb.query(cmdlist.get(2), cmdlist.get(3));
			
			send("QUERY-REPLY " + id + " "+ result.size());
			
			int i = 1;
			for(String str: result)
			{
				send("RESPONSE " + id + " " + i++ + " " + str);
			}
			return true;
		}
		
		
		if(cmdlist.get(0).equalsIgnoreCase("BYE"))
		{
			send("LATER");
			server.removeServerThread(this);
			return false;
		}
		
		if(cmdlist.get(0).equalsIgnoreCase("SHUTDOWN"))
		{
			send("OK");
			server.shutdown();
		}
		
		//Default case
		error(UNKNOWN_PROTOCOL_ERR);
		return true;
	}
	
	private void process() throws IOException
	{
		if(!handshake())
			return;
			
		while(true)
		{
			String str = receive();
			if(str == null)
				break;
			log.info("Recieved " + str);
			try{
				if( !parse_and_run(str) )
					break;
			}catch(IOException e)
			{
				if(e.getMessage().equalsIgnoreCase("Incorrect Command"))
					continue; /*Do nothing already handled.*/
				log.error("0x004 IOException: "+ e);
			}
		}
	}
	
	/*
	 * Validates the handshake protocol.
	 * Returns false on failure and true on success.
	 */
	private boolean handshake() throws IOException
	{
		String str = receive();
		if(str == null)
			return false;
		
		str = str.trim();
		if( !str.equalsIgnoreCase("HELLO") )
		{
			error(PROTO_SEQUENCE_ERR);
			return false;
		}
		
		/* Ok Success */
		log.info("Got HELLO");	
		send("HI");
		return true;
	}
	
	@Override
	public void run() {
		
		try {
			process();
			sock.close();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
	}

}
